#include "module.h"
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

#include "token.h"
#include "ast.h"
#include "base.h"
#include "option.h"
#include "expr.h"
#include "stmt.h"
#include "buffer.h"
#include "common.h"
#include "intrinsic.h"

#include <set>
#include <string>
#include <map>
#include <stack>
#include <vector>

#if defined DEBUG || defined _DEBUG
 //#define PRINT_CALC_EXPR
// #define PRINT_STATEMENT
// #define DEBUG_CALC
#endif

#ifdef PRINT_STATEMENT
#define out_stmt(node) \
    do {\
        printf("calc: ");\
        fprint_location(stdout, &node->loc);\
        print_statement_s(node);\
    } while (false)
#else
#define out_stmt(...)
#endif

#ifdef DEBUG_CALC
#define dbg_calc(stmt) print_statement_s(stmt)
#else
#define dbg_calc(...)
#endif

static std::string search_module(const char* name);

struct runtime_symbols
{
    std::set<std::string>* g_files = new std::set<std::string>;
    std::map<std::string, any_t>* g_symbols = new std::map<std::string, any_t>;
    std::map<std::string, method_t*>* g_methods = new std::map<std::string, method_t*>;
    std::map<std::string, class_def_t*>* g_classes = new std::map<std::string, class_def_t*>;
    std::vector<std::string>* g_modules = new std::vector<std::string>;
    std::string* g_current_module = new std::string("");
    ~runtime_symbols() {
        delete g_files;
        delete g_symbols;
        delete g_methods;
        delete g_classes;
        delete g_modules;
        delete g_current_module;
    }
};

// use pointer to make memory leak checking clear
runtime_symbols* g_globals = new runtime_symbols;

auto& g_files = *g_globals->g_files;
auto& g_symbols = *g_globals->g_symbols;
auto& g_methods = *g_globals->g_methods;
auto& g_classes = *g_globals->g_classes;
auto& g_modules = *g_globals->g_modules;
auto& g_current_module = *g_globals->g_current_module;

class_def_t* g_class_array;
class_def_t* g_class_dict;
class_def_t* g_class_string;

class env_t;

static method_t* get_internal_method(const char* name);

static void make_local_method(method_t & method, const char* name, fn_t fn, int param, void* bind = nullptr);

static bool exists(const std::string& path) {
    return file_exists(path.c_str());
}

static bool calc_has_error = false;

void bak_runtime();

void restore_runtime();

#define rmember(t, n) ((t)[(t).size() - 1 - (n)])

any_t calc_statement(env_t* env, ast_node_t* node);

any_t do_calc_statement(env_t* env, ast_node_t* node);

void print_stack(env_t* env)
{
    while (env) {
        fprint_location(stdout, &env->loc);
        printf(" %s\n", env->name);
        env = env->get_parent();
    }
}

class_def_t* get_class(const char* name)
{
    auto iter = g_classes.find(name);
    if (iter != g_classes.end()) {
        return iter->second;
    }
    else {
        return nullptr;
    }
}

any_t* env_t::get_expr_ref(expr_t* expr, bool with_warning, bool make)
{
    if (expr->op == OP_ID) {
        auto ref = get_value_ref(ref_to_string(&expr->token.ss));
        if (ref != nullptr) {
            return ref;
        } else {
            ref = get_value_ref("self");
            if (ref != nullptr && ref->type == atype_t::ANY_CLASS) {
                auto index = ref->obj->get_field_index(ref_to_string(&expr->token.ss));
                if (index >= 0) {
                    return ref->obj->get(index);
                }
            }
        }
    }
    return do_get_expr_ref(expr, with_warning, make);
}

any_t* env_t::do_get_expr_ref(expr_t* expr, bool with_warning, bool make)
{
    switch (expr->op) {
    case OP_ID: {
        auto ref = get_value_ref(ref_to_string(&expr->token.ss));
        if (ref == nullptr && make) {
            // a unknown var considered as declared as 0
            if (with_warning) {
                mark_line();
                do_warning(&expr->token.loc, "can't get var value: %s, assuming 0\n",
                    ref_to_string(&expr->token.ss));
            }
            put_value(ref_to_string(&expr->token.ss), any_t::zero());
            ref = get_value_ref(ref_to_string(&expr->token.ss));
        }
        return ref;
    } case OP_INDEX: {
        assert(expr->kids[0] && expr->kids[1]);
        auto array = calc_expr(this, expr->kids[0]);
        if (array.type != atype_t::ANY_STRING && array.type != atype_t::ANY_ARRAY && array.type != atype_t::ANY_DICT) {
            mark_line();
            set_calc_error();
            do_error(&tokc.loc, "expect array here: ");
            print_expression_s(expr->kids[0]);
            return nullptr;
        }
        auto index = calc_expr(this, expr->kids[1]);
        if (index.type != atype_t::ANY_I8 && array.type != atype_t::ANY_DICT) {
            mark_line();
            set_calc_error();
            do_error(&tokc.loc, "expect int value here: ");
            print_expression_s(expr->kids[1]);
            return nullptr;
        }
        if (array.type == atype_t::ANY_STRING) {
            return nullptr;
        } else if (array.type == atype_t::ANY_ARRAY) {
            if (index.i8 < 0 || index.i8 > array.array->get_count()) {
                mark_line();
                set_calc_error();
                do_error(&tokc.loc, "range exceed the array length: %d", index.i8);
                print_expression_s(expr->kids[1]);
                return nullptr;
            }
            return array.array->get((int)index.i8);
        } else if (array.type == atype_t::ANY_DICT) {
            auto ret = array.dict->find(index);
            if (ret == nullptr) {
                array.dict->insert(index, any_t());
            }
            return array.dict->find(index);
        } else {
            assert(0);
            return nullptr;
        }
    } case OP_POSTINC: {
    } case OP_POSTDEC: {
    } case OP_PREINC: {
    } case OP_PREDEC: {
        return get_expr_ref(expr->kids[0], with_warning);
    } case OP_MEMBER: {
        auto ref = get_expr_ref(expr->kids[0], false, false);
        if (ref == nullptr) {
            mark_line();
            do_error(&expr->kids[0]->loc, "the var not defined");
            return nullptr;
        }
        if (ref->type != atype_t::ANY_CLASS) {
            mark_line();
            do_error(&expr->kids[0]->loc, "the var is not a class object");
            return nullptr;
        }
        if (ref->get_obj() == nullptr || ref->get_obj()->get(0) == nullptr) {
            mark_line();
            do_error(&expr->kids[0]->loc, "NULL reference exception");
            return nullptr;
        }
        const char* name = ref_to_string(&expr->kids[1]->token.ss);
        if (expr->kids[1]->op == OP_ID) {
            return ref->get_obj()->get(name);
        } else if (expr->kids[1]->op == OP_CALL) {
            mark_line();
            do_error(&expr->kids[0]->loc, "unexpected op_member: %s", name);
            return nullptr;
            //return new any_t(ref->get_obj()->get_method(name));
        } else {
            mark_line();
            do_error(&expr->kids[0]->loc, "unexpected op_member's right operator");
            return nullptr;
        }
    } default: {
        mark_line();
        (void)expr->op;
        set_calc_error();
        do_error(&tokc.loc, "don't know how to get ref:");
        print_expression_s(expr->kids[1]);
        return nullptr;
    }
    }
    return nullptr;
}

method_t g_default_constructor;

static void init_fields(env_t* parent, env_t* env, class_obj_t* self, class_def_t* clz)
{
    if (clz->parent) {
        init_fields(parent, env, self, clz->parent);
    }
    for (int i = 0; i < sv_count(clz->fields); ++i) {
        auto field = clz->fields[i];
        if (field->init == nullptr) {
            self->set(field->index, any_t::none());
        } else {
            self->set(field->index, calc_expr(parent, field->init));
        }
    }
}

static void make_new_object(env_t* parent, env_t* env, expr_t* expr, void* bind)
{
    class_def_t* clz = (class_def_t*)bind;
    class_obj_t* self = class_obj_t::create(clz->get_field_count(), clz);
    init_fields(parent, env, self, clz);
    env->push_local(any_t(atype_t::ANY_CLASS, self));
    // env->return_done = true;
    env->set_symbol("self", 0);
}

static int count_expr(expr_t* expr)
{
    int ret = 0;
    while (expr) {
        ret++;
        expr = (expr_t*)expr->next;
    }
    return ret;
}

method_t* get_method(env_t* parent, env_t* env, expr_t* expr)
{
    method_t* method = nullptr;
    assert(expr->op == OP_CALL);
    if (expr->kids[0] == nullptr) {
        mark_line();
        do_error(&expr->loc, "expect function name in expression kids[0]");
        return nullptr;
    }
    auto argc = count_node(expr->kids[1]);
    if (expr->kids[0]->op == OP_MEMBER) {
        // search method
        auto a = calc_expr(parent, expr->kids[0]->kids[0]);
        if (a.type != atype_t::ANY_CLASS &&
            a.type != atype_t::ANY_ARRAY &&
            a.type != atype_t::ANY_DICT &&
            a.type != atype_t::ANY_STRING) {
            mark_line();
            set_calc_error();
            do_error(&expr->kids[0]->loc, "the var is not a class object:");
            print_expression_s(expr->kids[0]->kids[0]);
            return nullptr;
        }
        auto name = ref_to_string(&expr->kids[0]->kids[1]->token.ss);
        method = a.get_obj()->get_method(name, argc + 1);
        env->push_local(a);
        return method;
    } else if (expr->kids[0]->op == OP_ID) {
        char* name = ref_to_mstring(&expr->kids[0]->token.ss);
        auto_free f(name);
        auto iter = g_classes.find(name);
        if (iter != g_classes.end()) {
            class_def_t* clz = iter->second;
            method = clz->get_constructor(argc + 1);
            if (method == nullptr) {
                if (argc == 0) {
                    // default constructor is ok
                    return nullptr;
                }
                mark_line();
                set_calc_error();
                do_error(&expr->kids[0]->token.loc,
                    "failed to find the constructor %s with %d parameters\n",
                    name, argc);
                return nullptr;
            }
            return method;
        }
        auto fn = get_internal_method(name);
        if (fn != nullptr) {
            return fn;
        }
        auto iter2 = g_methods.find(name);
        if (iter2 != g_methods.end()) {
            return (method_t*)iter2->second;
        }
        auto ref = parent->get_value_ref("self");
        if (ref != nullptr && ref->type == atype_t::ANY_CLASS) {
            auto method = ref->obj->get_method(ref_to_string(&expr->token.ss), argc + 1);
            if (method != nullptr) {
                env->push_local(*ref);
                return method;
            }
        }
        mark_line();
        set_calc_error();
        do_error(&expr->kids[0]->token.loc, "failed to find the method or class %s\n", name);
        return nullptr;
    } else {
        mark_line();
        set_calc_error();
        do_error(&expr->kids[0]->token.loc, "expect a member or global method:");
        print_expression_s(expr->kids[0]);
        return nullptr;
    }
}

void enter_function(env_t* parent, env_t* env, expr_t* expr)
{
    assert(expr != nullptr);
    if (expr == nullptr) {
        return;
    }
    bool new_obj_made = false;
    if (expr->kids[0]->op == OP_ID && 
        g_classes.find(ref_to_string(&expr->kids[0]->token.ss)) != g_classes.end()) {
        class_def_t* clz = g_classes[ref_to_string(&expr->kids[0]->token.ss)];
        if (!clz->is_internal_class) {
            make_new_object(parent, env, expr, clz);
            new_obj_made = true;
        }
    }
    auto method = get_method(parent, env, expr);
    if (method == nullptr) {
        if (new_obj_made) {
            env->return_done = true;
        }
        else {
            mark_line();
            set_calc_error();
            do_error(&expr->loc, "failed to find method");
        }
        return;
    }
    env->return_done = false;   // global constructor set this value
    env->name = method->name;
    // if (strcmp(method->name, "ast_t") == 0) {
    //     printf("break at ast_t\n");
    // }
    // push arguments
    bool is_swap = strcmp(method->name, "swap") == 0;
    bool is_dict = strcmp(method->name, "dict") == 0;
    auto expr_arg = expr->kids[1];
    while (expr_arg) {
        if (is_swap) {
            auto arg = parent->get_expr_ref(expr_arg, true);
            if (arg == nullptr) {
                arg = parent->get_expr_ref(expr_arg, true);
            }
            env->push_local({ atype_t::ANY_REF, arg });
        } else if (expr_arg->op == OP_SPECIFY) {
            if (is_dict) {
                any_t a;
                a.type = atype_t::ANY_PAIR;
                a.pair = new pair_t;
                a.pair->first = calc_expr(parent, expr_arg->kids[0]);
                a.pair->second = calc_expr(parent, expr_arg->kids[1]);
                env->push_local(a);
            } else {
                mark_line();
                set_calc_error();
                do_error(&expr_arg->loc, "function argument specified not supported now!");
                return;
            }
        } else {
            auto a = calc_expr(parent, expr_arg);
            env->push_local(a);
        }
        expr_arg = (expr_t*)expr_arg->next;
    }

    // link arguments and names
    if (method->count() != -1 && (int)env->local_count() != method->count()) {
        mark_line();
        set_calc_error();
        do_error(&expr->kids[0]->token.loc, "parameter count not match the method: %s"
            ", %d needed, while has %d\n", method->name, method->count(), (int)env->local_count());
        return;
    }
    for (int i = 0; i < method->count(); ++i) {
        // order <----
        // env->set_symbol(method->params[i], env->local_count() - 1 - i);
        // order: ---->
        if (method->params && method->params[i]->name)
            env->set_symbol(method->params[i]->name, i);
    }
    if (method->is_local) {
        method->fn.fn(parent, env, expr, method->fn.bind);
        env->return_done = true;
    } else {
        for (int i = 0; i < sv_count(method->statements); ++i)
        {
            calc_statement(env, method->statements[i]);
            if (env->return_done)
                break;
        }
    }
    if (method->is_constructor) {
        auto ret = env->get_local(0);
        env->clear();
        env->push_local(ret);
        env->return_done = true;
    }
    if (!env->return_done) {
        env->clear();
    }
}

any_t leave_function(
    env_t* env, expr_t* expr)
{
    (void)expr;
    assert(env->local_count() == 1);
    return env->local_back();
}

any_t do_calc_expr(void* p_env, ast_node_t* node, bool left = false);

static int depth = 0;

static void print_tab(int tab)
{
    for (int i = 0; i < tab; ++i)
    {
        printf("|   ");
    }
}

inline any_t calc_expr(void* p_env, ast_node_t* node, bool left)
{
    static int i = 0;
    int bak = i++;
    if (g_option.debug_expr) {
        if (str_end_of(node->loc.filename, "regex.pcc") &&
            node->loc.line == 0x20) {
            printf("break here: ");
            printf("\n");
        }
        fprint_location(stdout, &node->loc);
        printf("\n");
        printf("%04d. ", bak);
        print_tab(depth);
        printf("calc: ");
        print_expression_s(node);
        printf("\n");
        ++depth;
    }
    auto a = do_calc_expr(p_env, node, left);
    if (g_option.debug_expr) {
        --depth;
        printf("%04d. ", bak);
        print_tab(depth);
        printf("-got: ");
        print_any(a);
        printf("\n");
        if (bak == 40) {
            printf("post break here\n");
        }
    }
    return a;
}
any_t do_calc_expr(void* p_env, ast_node_t* node, bool left)
{
    if (calc_error_occured()) {
        return any_t();
    }
    env_t* env = (env_t*)p_env;
    expr_t* expr = (expr_t*)node;
    any_t ret;
    switch (expr->op) {
    case OP_CONST: {
        if (expr->ty->categ == INT) {
            ret.type = atype_t::ANY_I8;
            ret.i8 = expr->token.i8;
        } else if (expr->ty->categ == FLOAT) {
            ret.type = atype_t::ANY_DOUBLE;
            ret.d = expr->token.d;
        } else {
            printf("error: unexpected expr const type:\n");
            print_expression(expr);
        }
        return ret;
    }
    case OP_LPAREN: {
        return calc_expr(env, expr->kids[1]);
    }
    case OP_ADD: {
        const any_t& a = calc_expr(env, expr->kids[0]);
        const any_t& b = calc_expr(env, expr->kids[1]);
        return add_any(a, b);
    }
    case OP_SUB: {
        return sub_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
    }
    case OP_MUL: {
        return mul_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
    }
    case OP_DIV: {
        return div_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
    }
    case OP_MOD: {
        return mod_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
    }
    case OP_ASSIGN: {
        auto a = calc_expr(env, expr->kids[1]);
        env->put_expr(expr->kids[0], a);
        return a;
    }
    case OP_ADD_ASSIGN: {
        auto a = add_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        env->put_expr(expr->kids[0], a);
        return a;
    }
    case OP_SUB_ASSIGN: {
        auto a = sub_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        env->put_expr(expr->kids[0], a);
        return a;
    }
    case OP_MUL_ASSIGN: {
        auto a = mul_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        env->put_expr(expr->kids[0], a);
        return a;
    }
    case OP_DIV_ASSIGN: {
        auto a = div_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        env->put_expr(expr->kids[0], a);
        return a;
    }
    case OP_MOD_ASSIGN: {
        auto a = mod_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        env->put_expr(expr->kids[0], a);
        return a;
    }
    case OP_ID: {
        return env->get_expr(expr);
    }
    case OP_PREINC: {
        auto a = env->get_expr_ref(expr->kids[0]);
        assert(a->type == atype_t::ANY_I8);
        a->i8 += 1;
        return *a;
    }
    case OP_POSTINC: {
        auto a = env->get_expr_ref(expr->kids[0]);
        assert(a->type == atype_t::ANY_I8);
        auto b = *a;
        a->i8 += 1;
        return b;
    }
    case OP_PREDEC: {
        auto a = env->get_expr_ref(expr->kids[0]);
        assert(a->type == atype_t::ANY_I8);
        a->i8 -= 1;
        return *a;
    }
    case OP_POSTDEC: {
        auto a = env->get_expr_ref(expr->kids[0]);
        assert(a->type == atype_t::ANY_I8);
        auto b = *a;
        a->i8 -= 1;
        return b;
    }
    case OP_EQUAL: {
        return equal_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
    }
    case OP_IS: {
        any_t* a = env->get_expr_ref(expr->kids[0], false, false);
        any_t name = calc_expr(env, expr->kids[1]);
        if (name.type != atype_t::ANY_STRING) {
            mark_line();
            set_calc_error();
            do_error(&expr->kids[1]->loc, "expect string type here");
            return any_t();
        }
        if ((a == nullptr && strcmp(name.s, "none"))
            || (a && strcmp(name.s, get_type_name(a->type)) == 0)) {
            return { atype_t::ANY_BOOL, 1 };
        } else {
            return { atype_t::ANY_BOOL, 0 };
        }
    }
    case OP_LESS: {
        return less_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
    }
    case OP_LESS_EQ: {
        return le_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
    }
    case OP_GREAT: {
        auto a = le_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        a.i8 = !a.i8;
        return a;
    }
    case OP_GREAT_EQ: {
        auto a = less_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        a.i8 = !a.i8;
        return a;
    }
    case OP_UNEQUAL: {
        auto a = equal_any(calc_expr(env, expr->kids[0]), calc_expr(env, expr->kids[1]));
        a.i8 = !a.i8;
        return a;
    }
    case OP_AND: {
        auto a = calc_expr(env, expr->kids[0]);
        auto b = calc_expr(env, expr->kids[1]);
        return any_t(atype_t::ANY_BOOL, a.i8 != 0 && b.i8 != 0);
    }
    case OP_OR: {
        auto a = calc_expr(env, expr->kids[0]);
        auto b = calc_expr(env, expr->kids[1]);
        return any_t(atype_t::ANY_BOOL, a.i8 != 0 || b.i8 != 0);
    }
    case OP_STR: {
        ret.type = atype_t::ANY_STRING;
        ret.s = expr->token.s;
        return ret;
    }
    case OP_CHAR: {
        ret.type = atype_t::ANY_CHAR;
        ret.i8 = expr->token.i8;
        return ret;
    }
    case OP_BOOL: {
        ret.type = atype_t::ANY_BOOL;
        ret.i8 = expr->val.i8;
        return ret;
    }
    case OP_NULL: {
        return { atype_t::ANY_NONE, 0 };
    }
    case OP_CALL: {
        auto p = new env_t;
        auto& fn_env = *p;
        fn_env.set_parent(env);
        fn_env.loc = expr->loc;
        enter_function(env, &fn_env, expr);
        if (fn_env.local_count() > 0) {
            ret = fn_env.local_back();
        } else {
            ret.type = atype_t::ANY_VOID;
            ret.s = 0;
        }
        fn_env.clear();
        delete p;
        return ret;
    }
    case OP_SPECIFY:
    case OP_COMMA: {
        ret.type = atype_t::ANY_VOID;
        ret.s = 0;
        assert(0);  // todo: to calc call expression?
        return ret;
    }
    case OP_INDEX: {
        if (left) {
            auto a = env->get_expr_ref(expr, true);
            if (a == nullptr) {
                return any_t();
            }
            else {
                return *a;
            }
        }
        else {
            assert(expr->kids[0] && expr->kids[1]);
            auto array = calc_expr(env, expr->kids[0]);
            if (array.type != atype_t::ANY_STRING && array.type != atype_t::ANY_ARRAY && array.type != atype_t::ANY_DICT) {
                mark_line();
                set_calc_error();
                do_error(&tokc.loc, "expect array here: ");
                print_expression_s(expr->kids[0]);
                return any_t();
            }
            auto index = calc_expr(env, expr->kids[1]);
            if (index.type != atype_t::ANY_I8 && array.type != atype_t::ANY_DICT) {
                mark_line();
                set_calc_error();
                do_error(&tokc.loc, "expect int value here: ");
                fprint_location(stdout, &expr->kids[1]->loc);
                print_expression_s(expr->kids[1]);
                return any_t();
            }
            if (array.type == atype_t::ANY_STRING) {
                return any_t(atype_t::ANY_CHAR, array.s[index.i8]);
            }
            else if (array.type == atype_t::ANY_ARRAY) {
                if (index.i8 < 0 || index.i8 > array.array->get_count()) {
                    mark_line();
                    set_calc_error();
                    do_error(&tokc.loc, "range exceed the array length: %d", index.i8);
                    print_expression_s(expr->kids[1]);
                    return any_t();
                }
                return *array.array->get((int)index.i8);
            }
            else if (array.type == atype_t::ANY_DICT) {
                auto ret = array.dict->find(index);
                if (ret == nullptr) {
                    array.dict->insert(index, any_t());
                    ret = array.dict->find(index);
                }
                return *ret;
            }
            else {
                assert(0);
                return any_t();
            }
        }
    }
    // case OP_SPECIFY: {
    //     any_t a;
    //     a.type = atype_t::ANY_PAIR;
    //     a.pair = new pair_t;
    //     a.pair->first.type = atype_t::ANY_STRING;
    //     a.pair->first.p = (void*)"";
    //     a.pair->second = env->get_expr(expr->kids[1]);
    //     return a;
    // }
    case OP_NEG: {
        auto a = calc_expr(env, expr->kids[0]);
        return neg_any(a);
    }
    case OP_POS: {
        return calc_expr(env, expr->kids[0]);
    }
    case OP_NOT: {
        auto a = calc_expr(env, expr->kids[0]);
        return not_any(a);
    }
    case OP_COMP: {
        auto a = calc_expr(env, expr->kids[0]);
        a.i8 = ~a.i8;
        return a;
    }
    case OP_QUESTION: {
        auto a = calc_expr(env, expr->kids[0]);
        if (is_number(a)) {
            if (equal_any(a, (int64_t)0).i8) {
                return calc_expr(env, expr->kids[1]->kids[1]);
            } else {
                return calc_expr(env, expr->kids[1]->kids[0]);
            }
        } else if (a.type == atype_t::ANY_STRING) {
            if (a.s) {
                return calc_expr(env, expr->kids[1]->kids[0]);
            } else {
                return calc_expr(env, expr->kids[1]->kids[1]);
            }
        } else {
            mark_line();
            set_calc_error();
            do_error(&expr->kids[0]->loc, "expect expr can be cast to bool");
            return any_t();
        }
        return a;
    }
    case OP_MEMBER: {
        auto ref = env->get_expr_ref(expr->kids[0], false, false);
        if (ref == nullptr) {
            mark_line();
            set_calc_error();
            do_error(&expr->kids[0]->loc, "the var not defined");
            return any_t();
        }
        if (ref->type != atype_t::ANY_CLASS) {
            mark_line();
            set_calc_error();
            do_error(&expr->kids[0]->loc, "the var is not a class object");
            return any_t();
        }
        if (ref->get_obj() == nullptr || ref->get_obj()->get(0) == nullptr) {
            mark_line();
            set_calc_error();
            do_error(&expr->kids[0]->loc, "NULL reference exception");
            return any_t();
        }
        const char* name = ref_to_string(&expr->kids[1]->token.ss);
        if (expr->kids[1]->op == OP_ID) {
            auto a = ref->get_obj()->get(name);
            if (a == nullptr) {
                mark_line();
                set_calc_error();
                do_error(&expr->kids[0]->loc, "can't find a member '%s' in class '%s'\n",
                    name, ref->get_obj()->get_type()->name);
                return any_t();
            }
            return *a;
        } else if (expr->kids[1]->op == OP_CALL) {
            auto method = ref->get_obj()->get_method(name, 0);
            env_t fn_env;
            fn_env.set_parent(env);
            fn_env.push_local(*ref);
            enter_function(env, &fn_env, expr->kids[1]);
            if (fn_env.local_count() > 0) {
                ret = fn_env.local_back();
            } else {
                ret.type = atype_t::ANY_VOID;
                ret.s = 0;
            }
            fn_env.clear();
            return ret;
        } else {
            mark_line();
            set_calc_error();
            do_error(&expr->kids[1]->loc, "unexpected op_member's right operator");
            return any_t();
        }
    }
    default:
        set_calc_error();
        mark_line();
        set_calc_error();
        do_error(&tokc.loc, "unexpected expr op: %s", get_op_name(expr->op));
        if (get_ast_error_count() > 100) {
            printf("too many errors, exit!\n");
            exit(-1);
        }
        return any_t();
    }
}

env_t* g_top_env = new env_t;

static int init_top_env()
{
    memset(&g_top_env->loc, 0, sizeof(g_top_env->loc));
    g_top_env->name = "<module>";
    g_top_env->loc.filename = g_top_env->name;
    return 0;
}

static int g_init_top = init_top_env();

inline any_t calc_statement(ast_node_t* node)
{
    return calc_statement(g_top_env, node);
}

void do_debug(env_t* env, ast_node_t* node, int depth);

inline any_t calc_statement(env_t* env, ast_node_t* node)
{
    static int i = 0;
    int bak = i++;
    if (g_option.debug) {
        ++depth;
        env->loc = node->loc;
        do_debug(env, node, depth);
    }
    auto a = do_calc_statement(env, node);
    if (g_option.debug) {
        --depth;
        // print_tab(tab);
        // printf("-got: ");
        // print_any(a);
        // // printf(" of %04d\n", bak);
        // printf("\n");
        //if (bak == 40) {
        //    printf("post break here\n");
        //}
    }
    return a;
}

any_t do_calc_statement(env_t* env, ast_node_t* node)
{
    out_stmt(node);
    if (calc_error_occured()) {
        return any_t();
    }
    switch (node->kind) {
    case NK_IfStatement: {
        if_stmt_t* stmt = (if_stmt_t*)node;
        any_t a = calc_expr(env, stmt->cond);
        if (a.i8) {
            calc_statement(env, stmt->then_stmt);
        } else {
            if (stmt->else_stmt) {
                calc_statement(env, stmt->else_stmt);
            }
        }
        return any_t();
    } case NK_WhileStatement: {
        while_stmt_t* stmt = (while_stmt_t*)node;
        any_t a = calc_expr(env, stmt->cond);
        while (a.type != atype_t::ANY_VOID && a.i8 != 0) {
            calc_statement(env, stmt->stmt);
            if (env->break_done) {
                env->break_done = false;
                break;
            }
            a = calc_expr(env, stmt->cond);
            if (calc_error_occured()) {
                break;
            }
        }
        return any_t();
    } case NK_ForStatement: {
        for_stmt_t* stmt = (for_stmt_t*)node;
        if (stmt->init) {
            calc_statement(env, stmt->init);
        }
        for (;;) {
            bool go_on = false;
            if (stmt->cond == nullptr) {
                go_on = true;
            } else {
                auto ret = calc_statement(env, stmt->cond);
                if (ret.type == atype_t::ANY_VOID) {
                    mark_line();
                    do_error(&stmt->cond->loc, "statement requires expression of scalar type ('void' invalid)");
                } else {
                    go_on = ret.i8 != 0;
                }
            }
            if (go_on) {
                calc_statement(env, stmt->stmt);
                if (env->break_done) {
                    env->break_done = false;
                    break;
                }
                if (env->return_done) {
                    break;
                }
                if (stmt->post) {
                    calc_statement(env, stmt->post);
                }
            } else {
                break;
            }
        }
        return any_t();
    } case NK_CompoundStatement: {
        block_stmt_t* stmt = (block_stmt_t*)node;
        for (int i = 0; i < sv_count(stmt->statements); ++i) {
            calc_statement(env, stmt->statements[i]);
            if (env->return_done) {
                break;
            }
            if (env->break_done) {
               break;
            }
        }
        break;
    } case NK_ExpressionStatement: {
        auto stmt = (expr_stmt_t*)node;
        return calc_expr(env, stmt->expr);
    } case NK_METHOD: {
        method_t* stmt = (method_t*)node;
        any_t ret;
        g_symbols[stmt->name] = ret;
        return ret;
    } case NK_Class: {
        auto stmt = (class_def_t*)node;
        if (stmt->default_constructor == nullptr) {
            method_t* method = new method_t;
            make_local_method(*method, "__init__", make_new_object, 1, (void*)node);
            stmt->default_constructor = method;
        }
        return any_t();
    } case NK_ReturnStatement: {
        return_stmt_t* stmt = (return_stmt_t*)node;
        auto a = calc_expr(env, stmt->expr);
        env->clear();
        env->push_local(a);
        env->return_done = true;
        return { atype_t::ANY_VOID, (int64_t)0 };
    } case NK_Global: {
        global_stmt_t* stmt = (global_stmt_t*)node;
        env->add_global(ref_to_string(&stmt->expr->token.ss));
        calc_expr(env, stmt->expr);
        return { atype_t::ANY_VOID, 0 };
    } case NK_BreakStatement: {
        env->break_done = true;
        return { atype_t::ANY_VOID, 0 };
    } case NK_Import: {
        import_stmt_t* stmt = (import_stmt_t*)node;
        auto ast = new ast_t;
        int ret = import_file(ast, stmt->token.s);
        if (ret != 0) {
            mark_line();
            set_calc_error();
            do_error(&node->token.loc, "can't load the module '%s'", node->token.s);
            delete ast;
            break;
        }
        stmt->ast = ast;
        return { atype_t::ANY_VOID, 0 };
    } default: {
        assert(0 && "unexpected statement type");
    }
    }
    return any_t();
}

any_t calc_node(ast_node_t* node)
{
    if (node->kind >= NK_ExpressionStatement && node->kind < NK_StatementEnd) {
        return calc_statement(node);
    } else if (node->kind == NK_Expression) {
        return calc_expr(&g_top_env, node);
    } else if (node->kind < NK_Expression) {
        return any_t();
    } else {
        assert(0 && "printing unexpected nodes");
        return any_t();
    }
}

std::map<std::string, any_t> g_bak_symbols;

void bak_runtime()
{
    g_bak_symbols = g_symbols;
}

void restore_runtime()
{
    g_symbols = g_bak_symbols;
    g_bak_symbols.clear();
}

int import_buffer(ast_t* ast, buffer_t* buf)
{
    int ret = parser_ast(ast, (char*)buf->data, buf->size, buf->name);
    if (ret != 0) {
        return ret;
    }
    ast->buf = buf;
    if (ast && g_option.dump_ast) {
        dump_ast(ast);
    }
    ast_node_t* node = ast->nodes;
    while (node)
    {
        auto next = node->next;
        if (node->kind == NK_METHOD) {
            method_t* stmt = (method_t*)node;
            g_methods[stmt->name] = stmt;
            //remove_node(&ast->nodes, stmt);
        }
        else if (node->kind == NK_Class) {
            auto* stmt = (class_def_t*)node;
            g_classes[stmt->name] = stmt;
            //remove_node(&ast->nodes, stmt);
        }
        if (g_option.dump_expr) {
            print_expr_stmt_s(node);
        }
        any_t v = calc_node(node);
            
        if (calc_error_occured()) {
            ret = -1;
            clear_calc_error();
            break;
        } else if (g_option.dump_expr && node->kind == NK_ExpressionStatement) {
            printf(" : ");
            print_any(v);
            printf("\n");
        }
        node = next;
    }
    return ret;
}

std::vector<std::string> g_system_path;

int import_file(ast_t* ast, const char* filename)
{
    buffer_t* buf = NULL;
    if (filename == NULL) {
        fprintf(stderr, "error: try to load null file name\n");
        return -1;
    }
    if (filename[0] == 0) {
        fprintf(stderr, "error: can't load a null name\n");
        return -1;
    }
    std::string name = filename;
    if (g_modules.size() > 0) {
        name = search_module(filename);
    }
    if (name.size() == 0) {
        fprintf(stderr, "error: can't find the module '%s'\n", filename);
        return -1;
    }

    if (is_directory(name.c_str())) {
        if (name.back() == '/') {
            g_system_path.push_back(name.substr(0, name.size() - 1));
            return 0;
        } else {
            fprintf(stderr, "error: the module to be imported is directory, add a '/' to it: '%s/'\n", filename);
            return -1;
        }
    }

    // first time do not add .pcc auto
    buf = read_to_buffer(name.c_str());
    if (!buf) {
        fprintf(stderr, "error: can't read the input file: %s\n", filename);
        return -1;
    }
    
    if (g_files.count(name) > 0) {
        fprintf(stderr, "recursive importing file: %s\n", filename);
        return -1;
    }
    g_files.insert(name);
    g_modules.push_back(name);

    bak_runtime();
    int ret = import_buffer(ast, buf);
    if (ret == 0) {
        g_current_module = name;
    } else {
        restore_runtime();
    }
    g_files.erase(name);
    g_modules.pop_back();
    if (g_option.dump_ref) {
        print_ref();
    }
    return ret;
}

struct internal_method
{
    method_t print;
    method_t println;
    method_t _array;
    method_t append;
    method_t len;
    method_t dict;
    method_t swap;
    method_t _typename;
    method_t has;
    method_t split;
    method_t get_tokens;
    method_t isupper;
    method_t islower;
};

internal_method* g_internal_methods_p = new internal_method;


static std::map<std::string, method_t*> g_internal_methods;

static void make_local_method(method_t& method, const char* name, fn_t fn, int param, void* bind)
{
    method.name = _strdup(name);
    method.is_local = true;
    method.param_count = param;
    method.fn.fn = fn;
    g_internal_methods[name] = &method;
}

static int install_methods()
{
    make_local_method(g_internal_methods_p->print, "print", do_intrinsic_print, -1);
    make_local_method(g_internal_methods_p->println, "println", do_intrinsic_println, -1);
    make_local_method(g_internal_methods_p->_array, "array", do_intrinsic_array, -1);
    make_local_method(g_internal_methods_p->append, "append", do_intrinsic_append, 2);
    make_local_method(g_internal_methods_p->len, "len", do_intrinsic_len, 1);
    make_local_method(g_internal_methods_p->dict, "dict", do_intrinsic_dict, -1);
    make_local_method(g_internal_methods_p->swap, "swap", do_intrinsic_swap, 2);
    make_local_method(g_internal_methods_p->_typename, "typename", do_intrinsic_typename, 1);
    make_local_method(g_internal_methods_p->has, "has", do_intrinsic_has, 2);
    make_local_method(g_internal_methods_p->split, "split", do_intrinsic_split, -1);
    make_local_method(g_internal_methods_p->get_tokens, "get_tokens", do_intrinsic_get_tokens, -1);
    make_local_method(g_internal_methods_p->isupper, "isupper", do_intrinsic_isupper, 1);
    make_local_method(g_internal_methods_p->islower, "islower", do_intrinsic_islower, 1);
    return 1;
}

static int methods_done = install_methods();

static class_def_t* make_internal_class(const char* name)
{
    auto clz = new class_def_t;
    clz->name = _strdup(name);
    clz->is_internal_class = true;
    g_classes[name] = clz;
    return clz;
}
static void add_method(class_def_t* clz, method_t* method)
{
    clz->push(method);
    if (strcmp(clz->name, method->name) == 0) {
        svs_push_ptr(&clz->constructors, method);
        if (method->count() == 1 || method->count() == -1)
            clz->default_constructor = method;
    }
}

static class_def_t* make_internal_class(const char* name, method_t* m1, method_t* m2 = nullptr)
{
    auto clz = make_internal_class(name);
    if (m1)
        add_method(clz, m1);
    if (m2)
        add_method(clz, m2);
    return clz;
}

static int install_classes()
{
    class_def_t* clz = nullptr;
    clz = make_internal_class("array");
    add_method(clz, &g_internal_methods_p->_array);
    add_method(clz, &g_internal_methods_p->len);
    add_method(clz, &g_internal_methods_p->append);
    add_method(clz, &g_internal_methods_p->has);
    g_class_array = clz;
    clz = make_internal_class("dict");
    add_method(clz, &g_internal_methods_p->dict);
    add_method(clz, &g_internal_methods_p->len);
    add_method(clz, &g_internal_methods_p->has);
    g_class_dict = clz;
    clz = make_internal_class("string");
    add_method(clz, &g_internal_methods_p->len);
    add_method(clz, &g_internal_methods_p->has);
    add_method(clz, &g_internal_methods_p->split);
    add_method(clz, &g_internal_methods_p->get_tokens);
    add_method(clz, &g_internal_methods_p->isupper);
    add_method(clz, &g_internal_methods_p->islower);
    g_class_string = clz;
    auto a = any_t("");
    a.get_obj()->set_type(g_class_string);
    return 1;
}

static int classes_done = install_classes();

static method_t* get_internal_method(const char* name)
{
    auto iter = g_internal_methods.find(name);
    if (iter != g_internal_methods.end()) {
        return iter->second;
    } else {
        return nullptr;
    }
}

static bool error_occured = false;

bool calc_error_occured()
{
    return error_occured;
}

void set_calc_error()
{
    error_occured = true;
}
void clear_calc_error()
{
    error_occured = false;
}

static void replace(std::string& ss, char src, char dst)
{
    char cs[2];
    cs[0] = dst;
    cs[1] = 0;
    auto pos = ss.find(src);
    while (pos != std::string::npos) {
        ss.replace(pos, 1, cs, 1);
        pos = ss.find(src);
    }
}

static std::string search_module(const char* name)
{
    assert(name);
    std::string org = name;
    std::string ss = name;
    if (org.size() == 0)
        return "";
    if (org.back() != '/' && !str_iend_of(name, ".pcc")) {
        ss += ".pcc";
    }
    if (name[0] == '\\' || name[0] == '/') {
        // abs path
        if (exists(org)) {
            return org;
        }
        else if (exists(ss)) {
            return ss;
        }
        else {
            return "";
        }
    }
    std::string parent_path = ".";
    if (g_modules.size() > 0) {
        auto parent = g_modules.back();
        int pos1 = (int)parent.rfind('\\');
        int pos2 = (int)parent.rfind('/');
        auto pos = pos1 > pos2 ? pos1 : pos2;
        if (pos != std::string::npos) {
            parent_path = parent.substr(0, pos);
        }
    }

    std::vector<std::string> all_path;
    all_path.push_back(parent_path);
    all_path.insert(all_path.end(), g_system_path.begin(), g_system_path.end());
    for (auto& s : all_path) {
        auto path = s + "/" + org;
        if (exists(path)) {
            return path;
        }
        auto path2 = path;
        replace(path2, '/', '\\');
        if (exists(path2)) {
            return path2;
        }
        path = s + "/" + ss;
        if (exists(path)) {
            return path;
        }
        if (exists(path)) {
            return path;
        }
        path2 = path;
        replace(path2, '/', '\\');
        if (exists(path2)) {
            return path2;
        }
    }
    return "";
}

void free_globals()
{
    delete g_top_env;
    g_top_env = nullptr;
    delete g_globals;
    g_globals = nullptr;
    delete g_internal_methods_p;
    g_internal_methods_p = nullptr;
    g_internal_methods.clear();
}
